#! /usr/bin/perl -w
#
# Probe for functional checking of MyProxy service
# Copyright (c) 2006 Emir Imamagic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# Changes and Modifications
# =========================
# 7-Jul-2006 - Created;
#              Algorithm:
#                1. Create proxy certificate if there is no existing
#                2. Create renewable MyProxy certificate
#                3. Query MyProxy service
#                4. Destroy MyProxy service
#
# 7-Mar-2007 - Use proxy to create MyProxy certificate instead of user credential
#            - Stored MyProxy certificate is now retrieveable
#
# 22-Mar-2007 - Removed hardcoded MyProxy certificate passphrase
#
# 26-Mar-2007 - Common libraries; plugin is WLCG-compatible
#
# 29-Mar-2007 - Added certificate lifetime metric
#
# 15-May-2007 - Added proxy lifetime metric
#
# 29-May-2007 - Updates related to WLCG specification changes
#
# 11-Jan-2008 - Added wrapper for safe execution of shell commands
#
# 24-May-2008 - Corrected hr.srce.MyProxy-ProxyLifetime to support
#               MyProxy certificates with defined policies (-r, -R, -Z)
#             - Added environment variable GT_PROXY_MODE=old, because
#               gLite services still use old style proxy. See:
#               https://gus.fzk.de/pages/ticket_details.php?ticket=26846
#
# 15-Sep-2008 - changed sgutils namespace
#
# 11-Mar-2009 - removed CertLifetime, performed by CertLifetime-probe
#             - name of stored proxy is unique
#
# 16-Dec-2009 - 1.11 Migrated to Apache 2.0 license
#
# 23-Aug-2010 - 1.12 Migrated to Nagios::Plugin.

use strict;
use Nagios::Plugin;
use Sys::Hostname;
use Date::Format;
use GridMon::sgutils qw($VONAME &checkProxy &processCommand);
use Sys::Syslog;
use TOM::Nagios qw(nagios_debug);

local $SIG{__DIE__}  = \&TOM::Nagios::handle_die;
local $SIG{__WARN__} = \&TOM::Nagios::handle_warn;
openlog("MyProxy-probe", "ndelay,pid", "user");
nagios_debug("started");

# Standard variables used in Nagios::Plugin constructor
use constant PROGNAME => "MyProxy-probe";
use constant VERSION => '1.12';
use constant DESCRIPTION => 'Probe for functional checking of MyProxy service';
use constant EXTRA_DESC => "";
use constant LICENSE => 'This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
   http://www.apache.org/licenses/LICENSE-2.0
Copyright 2009 Emir Imamagic';
use constant SHORTNAME => 'MyProxy';
use constant USAGE => "usage: $0 [ -v ] \n";
use constant DEFAULT_PORT => 7512;
use constant DEFAULT_METRIC => 'MyProxy-Store';
use constant DEFAULT_MYPROXY_USER => 'nagios';

my %COMMANDS;

sub processErrorOutput {
    my $res = shift;

    $res =~ s/^\s*\n//mg;

    #Your identity: ...
    #Creating proxy .......................................................... Done
    #Proxy Verify OK
    #Your proxy is valid until: Wed Apr  4 14:46:54 2007

    $res =~ s/^Your identity:\s+\/.*\n//mg;
    $res =~ s/^Creating proxy\s+\.+\s+Done.*\n//mg;
    $res =~ s/^Proxy\s+Verify\s+OK\n//mg;
    $res =~ s/^Your proxy is valid until:.*\n//mg;

    $res =~ s/\n/; /mg;

    $res;
}

sub processQueryOutput {
    my $res = shift;

    $res =~ s/^\s*\n//mg;

    $res =~ s/\n/; /mg;

    $res;
}

sub genPass {
    my @chars = ('a'..'z','A'..'Z','0'..'9','_','{','}','-','(',')',';','/');
    my $pass = "";

    srand (time ^ $$);
    for (my $i = 0; $i < 30; ++$i)
    {
        $pass .= $chars[rand @chars];
    }
    return $pass;
}

sub analyzeProxy {
    my $proxy = shift;
    my $verbose = shift;

    my $state = OK;
    my $answer;
    my $res;

	print "INFO: getting proxy certificate info\n" if ($verbose);
	print "COMMAND: $COMMANDS{GRID_PROXY_INFO} 2>&1\n" if ($verbose);

    ($state, $res) = processCommand ("$COMMANDS{GRID_PROXY_INFO} 2>&1");

    if ($state == CRITICAL) {
        $answer = "Proxy certificate error: " . processErrorOutput($res) . ".\n";
        $state  = UNKNOWN;
    } elsif ($state == UNKNOWN) {
        $answer = $res;
    } else {
		my $timeleft;
		if ($res =~ /^identity\s+:\s+(.+)$/mg) {
			$COMMANDS{MY_PROXY_INIT} .= " -r \"$1\" ";
		}
		if ($res =~ /^timeleft\s+:\s+(\d+?):\d\d:\d\d/mg) {
			$timeleft = $1;
		}
		if ( ! defined $timeleft ) {
			$answer = "Proxy certificate error - cannot determine lifetime.\n";
			$state  = UNKNOWN;
		} elsif ( $timeleft < 1 ) {
			$answer = "Proxy certificate error - lifetime too short: $timeleft.\n";
			$state  = UNKNOWN;
		} else {
			$ENV{'X509_USER_CERT'}=$proxy;
			$ENV{'X509_USER_KEY'}=$proxy;
		}
		$res = undef;
	}

	return ($state, $answer, $res);
}

sub createMyProxy {
    my $verbose = shift;

    my $state = OK;
    my $answer;
    my $res;
    my $pass = &genPass;

#    print "COMMAND: $COMMANDS{MY_PROXY_INIT}\n" if ($verbose);
	my $initcmd = "/bin/echo -en \"$pass\n\" | $COMMANDS{MY_PROXY_INIT}";
	if ($verbose) {
		$initcmd .= "-v";
		print "COMMAND: $initcmd\n";
	}

#    ($state, $res) = processCommand ("/bin/echo -en \"$pass\n$pass\n\" | $COMMANDS{MY_PROXY_INIT} 2>&1");
	($state, $res) = processCommand ($initcmd);



    if ($state == CRITICAL) {
        print "ERROR: proxy creation failed\n" if ($verbose);
        $answer = "Creating MyProxy credential failed: " . processErrorOutput($res) . ".\n";
    } elsif ($state == UNKNOWN) {
        $answer = $res;
    } else {
	    $answer = "MyProxy credential created. ";
	    $res = undef;
	}
	return ($state, $answer, $res);
}

sub queryMyProxy {
    my $name = shift;
    my $verbose = shift;

    my $state = OK;
    my $answer;
    my $res;

	print "COMMAND: $COMMANDS{MY_PROXY_INFO}\n" if ($verbose);

    ($state, $res) = processCommand ("$COMMANDS{MY_PROXY_INFO} 2>&1");

    if ($state == CRITICAL) {
        $answer = "Querying MyProxy credential failed: " . processQueryOutput($res) . ".\n";
    } elsif ($state == UNKNOWN) {
        $answer = $res;
    } else {
		if ($res =~ /name: $name/){
            $answer .= "Querying stored credential succeeded. ";
            $res = undef;
        } else {
            $answer = "Cannot find info for created MyProxy credential. ";
            $state = CRITICAL;
        }
	}
    return ($state, $answer);
}

sub deleteMyProxy {
    my $myProxyUser = shift;
    my $name = shift;
    my $verbose = shift;

    my $state = OK;
    my $answer;
    my $res;

	print "COMMAND: $COMMANDS{MY_PROXY_DESTROY}\n" if ($verbose);

    ($state, $res) = processCommand ("$COMMANDS{MY_PROXY_DESTROY} 2>&1");

    if ($state == CRITICAL) {
		$answer = "Destroying MyProxy credential failed: " . processErrorOutput($res) . ".\n";
    } elsif ($state == UNKNOWN) {
        $answer = $res;
    } else {
		if ($res =~ /^MyProxy credential '$name' for user $myProxyUser was successfully removed./){
            $answer .= "MyProxy credential destroyed. ";
            $res = undef;
		} else {
			$answer = "Destroy command successful, but output unknown: ". processErrorOutput($res) . ".\n";
			$state = CRITICAL;
		}
	}

    return ($state, $answer, $res);
}

sub queryMyProxyName {
    my $command = shift;
    my $name = shift || "";
    my $verbose = shift;

    my $state = OK;
    my $answer;
    my $res;

    print "COMMAND: $command\n" if ($verbose);

    ($state, $res) = processCommand ("$command 2>&1");
    #username: /C=XX/O=XXX/OU=XXX/CN=XXX XXXX
    #owner: /C=XX/O=XXX/OU=XXX/CN=XXX XXXX
    #  name: nagiosRefresh
    #  timeleft: 157:59:51  (6.6 days)

    if ($state == CRITICAL) {
        $answer = "Querying MyProxy credential failed: " . processQueryOutput($res) . ".\n";
        $state = UNKNOWN;
    } elsif ($state == UNKNOWN) {
        $answer = $res;
    } else {
        my $expr = 'timeleft: (\d+):(\d+):(\d+)';
        $expr = "name: $name" . '.*?' . $expr if ($name);

        if ($res =~ /$expr/s){
            $answer = "Querying stored credential succeeded. ";
            $res = $1 * 3600 + $2 * 60 + $3;
        } else {
            $answer = "Cannot find info for MyProxy credential $name.\n";
            $state = CRITICAL;
        }
    }
    return ($state, $answer, $res);
}

# Local variables
my ($state,$answer,$res);
my ($timeoutanswer,$proxyCreated,$tmpAnswer,$hostname,$port,$myProxyUser,$proxyName);

# Create Nagios::Plugin instance
my $plugin = Nagios::Plugin->new (usage => USAGE,
                                  shortname => SHORTNAME,
                                  version => VERSION,
                                  blurb => DESCRIPTION,
                                  extra => EXTRA_DESC,
                                  license => LICENSE,
                                  plugin  => PROGNAME);
# Define additional arguments
$plugin->add_arg(
    spec => 'hostname|H=s',
    help => "H|hostname\n   Name or IP address of host to check.",
    required => 0,
    default => 'localhost'
);
$plugin->add_arg(
    spec => 'port|p=i',
    help => "p|port\n   Port of the service.\n   (default: ".DEFAULT_PORT.")",
    required => 0,
    default => DEFAULT_PORT
);
$plugin->add_arg(
    spec => 'proxy|x=s',
    help => "proxy|x\n   Location of Nagios user's proxy file.\n   (default: /tmp/x509up_u$<)",
    required => 0,
    default => "/tmp/x509up_u$<"
);
$plugin->add_arg(
    spec => 'vo=s',
    help => "vo\n   Virtual organization of user.\n   (default: )",
    required => 0,
);
$plugin->add_arg(
    spec => 'metric|m=s',
    help => "metric|m
   Metric which should be executed.
   Possible values: MyProxy-Store, MyProxy-ProxyLifetime
   MyProxy-Store creates temp proxy and destroys it
   MyProxy-ProxyLifetime checks lifetime of existing proxy
   (default: MyProxy-Store)",
    required => 0,
    default => DEFAULT_METRIC
);
$plugin->add_arg(
    spec => 'name=s',
    help => "Name of MyProxy to check (in case ProxyLifetime metric).
   (Default: )",
    required => 0
);
$plugin->add_arg(
    spec => 'myproxyuser=s',
    help => "myproxyuser
   Name of MyProxy account under which the credential
   was stored.
   Probe uses MyProxy account (option -l) instead of user
   DNs (-d) because probe refresh the credential without
   knowing the DN of user certificate which was used for
   creating MyProxy credential.
   (Default: ".DEFAULT_MYPROXY_USER.")",
    required => 0,
    default => DEFAULT_MYPROXY_USER
);
$plugin->add_arg(
    spec => 'warning|w=i',
    help => "w|warning\n   Warning threshold for proxy expiration (min).\n   (default: 7200, 5 days)",
    required => 0,
    default => 7200
);
$plugin->add_arg(
    spec => 'critical|c=i',
    help => "c|critical\n   Critical threshold for proxy expiration (min).\n   (default: 1440, 1 day)",
    required => 0,
    default => 1440
);

$plugin->getopts;

$hostname = $plugin->opts->hostname;
$port = $plugin->opts->port;
$myProxyUser = $plugin->opts->myproxyuser;
$proxyName = $plugin->opts->name;

$VONAME = $plugin->opts->vo if ($plugin->opts->vo);

$plugin->nagios_die("Metric ".$plugin->opts->metric." is not supported.") 
    if ($plugin->opts->metric !~ /^(MyProxy-Store|MyProxy-ProxyLifetime)$/);

my $globusLocation = $ENV{GLOBUS_LOCATION} || "/usr";
$COMMANDS{MY_PROXY_INIT} = "$globusLocation/bin/myproxy-init";
$COMMANDS{MY_PROXY_INFO} = "$globusLocation/bin/myproxy-info";
$COMMANDS{MY_PROXY_DESTROY} = "$globusLocation/bin/myproxy-destroy";
$COMMANDS{GRID_PROXY_INFO} = "$globusLocation/bin/grid-proxy-info";

# Just in case of problems, let's not hang Nagios
local $SIG{ALRM} = sub {
    local $SIG{TERM} = 'IGNORE';
    kill TERM => -$$;
    $plugin->nagios_die($timeoutanswer);
};

local $SIG{TERM} = sub {
    local $SIG{TERM} = 'IGNORE';
    kill TERM => -$$;
    $plugin->nagios_die("Plugin received TERM signal.");
};

alarm($plugin->opts->timeout);

##### Main program

# gLite still uses old style proxy, see:
# https://gus.fzk.de/pages/ticket_details.php?ticket=26846
#$ENV{'GT_PROXY_MODE'}="old";

($state,$answer,$res) = checkProxy($plugin->opts->proxy);
if ( $state != OK) {
    $plugin->nagios_exit($state, $answer);
}

if ($plugin->opts->metric eq "MyProxy-ProxyLifetime") {
    use GridMon::certutils qw(&checkCertDiff);
    $state = OK;

    $timeoutanswer = "Timeout occured during MyProxy query.";
    ($state,$answer,$res) = queryMyProxyName("$COMMANDS{MY_PROXY_INFO} -s $hostname -p $port -l $myProxyUser", $proxyName, $plugin->opts->verbose);

    if ( $state == OK) {
        ($state,$answer) = checkCertDiff ($res, time2str("%b %d %H:%M:%S %Y %Z",time() + $res,"GMT"), $plugin->opts->warning, $plugin->opts->critical);
    }

    #Turn off alarm
    alarm(0);

    $plugin->nagios_exit($state,$answer);
}

if ($plugin->opts->metric eq "MyProxy-Store") {
    $state = OK;

    if (!$proxyName) {
        $proxyName = "nagiosMyProxyStore-" . hostname();
        if ($VONAME) {
            $proxyName .= "-$VONAME";
        }
    }

    $COMMANDS{MY_PROXY_INIT} .= " -x -S -k $proxyName -l $myProxyUser -c 1 -s $hostname -p $port";
    $COMMANDS{MY_PROXY_INFO} .= " -l $myProxyUser -s $hostname -p $port";
    $COMMANDS{MY_PROXY_DESTROY} .= " -k $proxyName -l $myProxyUser -s $hostname -p $port";

    ($state,$answer,$res) = analyzeProxy($plugin->opts->proxy, $plugin->opts->verbose);
    if ( $state != OK) {
        alarm(0);
        $plugin->nagios_exit($state,$answer);
    }

    $timeoutanswer = "Timeout occured during MyProxy credential creation.";
    ($state,$answer,$res) = createMyProxy($plugin->opts->verbose);
    if ( $state != OK) {
        alarm(0);
        $plugin->nagios_exit($state,$answer);
    }
    $timeoutanswer = "Timeout occured during MyProxy query.";
    ($state,$tmpAnswer,$res) = queryMyProxy($proxyName, $plugin->opts->verbose);
    $answer .= $tmpAnswer;
    if ( $state != OK) {
        alarm(0);
        $plugin->nagios_exit($state,$answer);
    }
    $timeoutanswer = "Timeout occured during MyProxy credential removal.";
    ($state,$tmpAnswer,$res) = deleteMyProxy($myProxyUser, $proxyName, $plugin->opts->verbose);
    $answer .= $tmpAnswer;
    if ( $state != OK) {
        alarm(0);
        $plugin->nagios_exit($state,$answer);
    }

    alarm(0);
}

$plugin->nagios_exit($state,"$answer");


